package com.firmye.codegenerator.core;

import com.firmye.codegenerator.model.DataModel;
import com.firmye.codegenerator.model.Field;
import com.firmye.codegenerator.model.Method;
import com.firmye.codegenerator.model.Sql;
import com.firmye.codegenerator.utils.ClassUtil;
import com.firmye.codegenerator.utils.ConnectionUtil;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import freemarker.template.utility.StringUtil;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.*;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
public class CodeGenerator {

    @Getter
    @Setter
    private String url = "";
    @Getter
    @Setter
    private String username = "";
    @Getter
    @Setter
    private String password = "";
    @Getter
    @Setter
    private String tableName = "%"; // 表名，%表示所有表
    @Getter
    @Setter
    private String rootPackage = "";    // 根目录
    @Getter
    @Setter
    private String[] packages = {"model.classname.pojo","dao","service","sqls"};
    @Getter
    @Setter
    private Boolean useLombok = true;
    @Getter
    @Setter
    private Boolean isSpringBoot = true;
    @Getter
    @Setter
    private Boolean isRollback = true;

    private DatabaseMetaData databaseMetaData;
    private ResultSet tableSet;
    private Configuration cfg;
    private String rootPath;
    private String resPath;
    private Method methodInsert;
    private Method methodUpdate;
    private Method methodQuery;

    public void generate() throws Exception {
        Connection conn = ConnectionUtil.getConnection(url,username,password);
        databaseMetaData = conn.getMetaData();

        /**
         * catalog: 数据库名，可为空
         * schemaPattern: 登录用户名，%表示所有用户
         * tableName: 表名，%表示所有表
         * types: 类型标准
         */
        tableSet = databaseMetaData.getTables(null, "%", tableName, new String[] { "TABLE" }); // 第三个参数为表名，%表示所有表
        // 把游标指向结果集末尾获取表个数再把游标指向第一行前
        tableSet.last();
        int length = tableSet.getRow();
        tableSet.beforeFirst();

        // 模板设置
        cfg = new Configuration(Configuration.VERSION_2_3_23);
        cfg.setDefaultEncoding("UTF-8");
        cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);

        // 进度标记
        int loading = 1;

        while (tableSet.next()) {
            log.debug("TableName : " + tableSet.getString("TABLE_NAME"));

            String tableName = tableSet.getString("TABLE_NAME");
            String[] strs = tableName.split("_");
            String className = "";
            String packageName = "";
            for (int i = 0; i < strs.length; i++) {
                className += StringUtil.capitalize(strs[i]);
                packageName += strs[i];
            }

            ResultSet pkSet = databaseMetaData.getPrimaryKeys(null, null, tableName);
            String pkName = null;
            while (pkSet.next()) {
                pkName = pkSet.getObject(4).toString();
                log.debug("PkName : " + pkSet.getObject(4).toString());
            }

            cfg.setDirectoryForTemplateLoading(new File("src/main/resources/code-templates"));

            rootPath = new File("").getCanonicalPath() + File.separator + "src" + File.separator + "main" + File.separator
                    + "java" + File.separator + rootPackage.replaceAll("\\.","\\" + File.separator) + File.separator;
            resPath = new File("").getCanonicalPath() + File.separator + "src" + File.separator + "main" + File.separator
                    + "resources" + File.separator;

            Map root = new HashMap();
            root.put("useLombok",useLombok);

            genModel(root,packageName,className);
            DataModel daoModel = genDao(root,packageName,className);
            genService(root,packageName,className,daoModel);
            genSql(root,className,pkName,daoModel);

            String loadingView = "";
            for (int i = 0; i < loading; i++) {
                loadingView += ">";
            }
            for (int i = 0; i < length - loading; i++) {
                loadingView += ".";
            }
            loading++;
            System.out.println(loadingView);

            System.out.println("Generate finish!!!");
        }
    }

    /**
     * 生成Mapper
     *
     * @param root
     * @param className
     * @param pkName
     * @param daoModel
     * @throws IOException
     * @throws TemplateException
     */
    private void genSql(Map root,String className,String pkName,DataModel daoModel) throws IOException, TemplateException {
        Sql sql = new Sql();
        sql.setNamespace(daoModel.getClassFullName());
        sql.setClassName(className);
        sql.setTableName(tableName);
        sql.setPkColumn(pkName);
        sql.setPkName(StringUtils.uncapitalize(pkName));

        root.put("sql", sql);

        Template template = cfg.getTemplate("sql.ftl");

        File sqlFile = new File(resPath + packages[3] + File.separator + className + "Dao.xml");
        File sqlPath = new File(resPath + packages[3]);

        if (!sqlPath.exists()) {
            sqlPath.mkdirs();
            sqlFile.createNewFile();
        }

        Writer writer = new FileWriter(sqlFile);
        template.process(root, writer);
        writer.flush();
        log.debug("Mapper文件生成路径:" + sqlFile.getCanonicalPath());
        writer.close();
    }

    /**
     * 生成Service层
     *
     * @param root
     * @param packageName
     * @param className
     * @param daoModel
     * @throws IOException
     * @throws TemplateException
     */
    private DataModel genService(Map root,String packageName,String className,DataModel daoModel) throws IOException, TemplateException {
        DataModel serviceModel = new DataModel();
        serviceModel.setPackageName(rootPackage + "." + packages[2]);
        serviceModel.addImport(List.class.getName());
        serviceModel.addAnno("@Service");
        serviceModel.addImport(Service.class.getName());
        serviceModel.setClassName(className + "Service");
        if (useLombok) {
            serviceModel.addAnno("@Slf4j");
            serviceModel.addImport(Slf4j.class.getName());
        }
        if (isRollback) {
            serviceModel.addAnno("@Transactional(rollbackFor = Exception.class)");
            serviceModel.addImport(Transactional.class.getName());
        }

        Field serviceField = new Field();
        serviceField.addAnno("@Autowired");
        serviceModel.addImport(Autowired.class.getName());
        serviceField.setFieldType(className + "Dao");
        serviceField.setFieldName(StringUtils.uncapitalize(className) + "Dao");
        serviceModel.addImport(daoModel.getClassFullName());
        serviceModel.addField(serviceField);

        methodInsert.setStatement("return " + serviceField.getFieldName() + "." + methodInsert.getMethodName() + "("
                + StringUtils.uncapitalize(className) + ")");
        methodUpdate.setStatement("return " + serviceField.getFieldName() + "." + methodUpdate.getMethodName() + "("
                + StringUtils.uncapitalize(className) + ")");
        methodQuery
                .setStatement("return " + serviceField.getFieldName() + "." + methodQuery.getMethodName() + "()");
        serviceModel.addMethod(methodInsert);
        serviceModel.addImport((rootPackage + "." + packages[0]).replaceAll("classname", packageName) + "." + className);
        serviceModel.addMethod(methodUpdate);
        serviceModel.addMethod(methodQuery);

        root.put("sm", serviceModel);

        Template template = cfg.getTemplate("service.ftl");

        File serviceFile = new File(rootPath + packages[2] + File.separator + className + "Service.java");
        File servicePath = new File(rootPath + packages[2]);

        if (!servicePath.exists()) {
            servicePath.mkdirs();
            serviceFile.createNewFile();
        }

        Writer writer = new FileWriter(serviceFile);
        template.process(root, writer);
        writer.flush();
        log.debug("Service文件生成路径:" + serviceFile.getCanonicalPath());
        writer.close();

        return serviceModel;
    }


    /**
     * 生成Dao层
     *
     * @param root
     * @param packageName
     * @param className
     * @throws IOException
     * @throws TemplateException
     */
    private DataModel genDao(Map root,String packageName,String className) throws IOException, TemplateException {
        DataModel daoModel = new DataModel();
        daoModel.setPackageName(rootPackage + "." + packages[1]);
        daoModel.addImport(List.class.getName());
        if (!isSpringBoot) {
            daoModel.addAnno("@Repository");
            daoModel.addImport(Repository.class.getName());
        }
        daoModel.setClassName(className + "Dao");

        methodInsert = new Method();
        methodInsert.setReturnTypeName("int");
        methodInsert.setMethodName("insert");
        Field param = new Field();
        param.setFieldType(className);
        param.setFieldName(StringUtils.uncapitalize(className));
        methodInsert.addParam(param);
        daoModel.addMethod(methodInsert);
        daoModel.addImport((rootPackage + "." + packages[0]).replaceAll("classname", packageName) + "." + className);

        methodUpdate = new Method();
        methodUpdate.setReturnTypeName("int");
        methodUpdate.setMethodName("update");
        param = new Field();
        param.setFieldType(className);
        param.setFieldName(StringUtils.uncapitalize(className));
        methodUpdate.addParam(param);
        daoModel.addMethod(methodUpdate);

        methodQuery = new Method();
        methodQuery.setReturnTypeName("List<" + className + ">");
        methodQuery.setMethodName("queryAll");
        daoModel.addMethod(methodQuery);

        root.put("dm", daoModel);

        Template template = cfg.getTemplate("dao.ftl");

        File daoFile = new File(rootPath + packages[1] + File.separator + className + "Dao.java");
        File daoPath = new File(rootPath + packages[1]);

        if (!daoPath.exists()) {
            daoPath.mkdirs();
            daoFile.createNewFile();
        }

        Writer writer = new FileWriter(daoFile);
        template.process(root, writer);
        writer.flush();
        log.debug("Dao文件生成路径:" + daoFile.getCanonicalPath());
        writer.close();

        return daoModel;
    }


    /**
     * 生成Model层
     *
     * @param root
     * @param packageName
     * @param className
     * @throws SQLException
     * @throws IOException
     * @throws TemplateException
     */
    private DataModel genModel(Map root, String packageName, String className) throws SQLException, IOException, TemplateException {
        DataModel modelModel = new DataModel();
        modelModel.setPackageName((rootPackage + "." + packages[0]).replaceAll("classname", packageName));
        modelModel.setClassName(className);
        modelModel.addImport(Serializable.class.getName());
        modelModel.addInterface(Serializable.class.getSimpleName());

        String columnName, columnType, columnRemarks;
        Class clazz;
        ResultSet columnSet = databaseMetaData.getColumns(null, "%", tableSet.getString("TABLE_NAME"), "%");
        while (columnSet.next()) {
            columnName = columnSet.getString("COLUMN_NAME");
            columnType = columnSet.getString("TYPE_NAME");
            columnRemarks = columnSet.getString("REMARKS");
            clazz = ClassUtil.getClass(columnType);
            String im = ClassUtil.getByClass(clazz);
            if (null != im) {
                modelModel.addImport(im);
            }
            log.debug("ColumnName : " + columnName);
            log.debug("ColumnType : " + columnType);
            log.debug("ColumnRemarks : " + columnRemarks);
            log.debug("Class : " + clazz.getName());

            Field modelField = new Field();
            modelField.setFieldType(clazz.getSimpleName());
            modelField.setFieldColumn(columnName);
            modelField.setFieldName(StringUtils.uncapitalize(columnName));
            modelField.setFieldRemarks(columnRemarks);
            modelModel.addField(modelField);

            Method methodGet = new Method();
            methodGet.setProperty("public");
            methodGet.setReturnTypeName(clazz.getSimpleName());
            methodGet.setMethodName("get" + StringUtils.capitalize(columnName));
            methodGet.setStatement("return this." + StringUtils.uncapitalize(columnName));
            modelModel.addMethod(methodGet);

            Method methodSet = new Method();
            methodSet.setProperty("public");
            methodSet.setReturnTypeName(void.class.getSimpleName());
            methodSet.setMethodName("set" + StringUtil.capitalize(columnName));
            Field param = new Field();
            param.setFieldType(clazz.getSimpleName());
            param.setFieldName(StringUtils.uncapitalize(columnName));
            methodSet.addParam(param);
            methodSet.setStatement("this." + StringUtils.uncapitalize(columnName) + " = " + StringUtils.uncapitalize(columnName));
            modelModel.addMethod(methodSet);

        }

        root.put("mm", modelModel);

        Template template = cfg.getTemplate("model.ftl");

        String tmpPath = packages[0].replaceAll("classname", packageName).replaceAll("\\.","\\" + File.separator) + File.separator;
        File modelFile = new File(rootPath + tmpPath + className + ".java");
        File modelPath = new File(rootPath + tmpPath);

        if (!modelPath.exists()) {
            modelPath.mkdirs();
            modelFile.createNewFile();
        }

        Writer writer = new FileWriter(modelFile);
        template.process(root, writer);
        writer.flush();
        log.debug("Model文件生成路径:" + modelFile.getCanonicalPath());
        writer.close();

        return modelModel;
    }
}
